/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */


package ru.teleology.cubefs.editor.hex;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;

import javax.swing.DefaultCellEditor;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import ru.teleology.cubefs.api.models.Page;
import ru.teleology.cubefs.api.reflection.metadata.IDField;
import ru.teleology.cubefs.api.reflection.metadata.PageMetadata.FieldDescriptor;
import ru.teleology.cubefs.editor.model.TreePageMetadataHelper;

public class HexTable extends JTable {
	private static final long serialVersionUID = -5387577468462945928L;

	private EventListenerList listenerList = new EventListenerList();
	
	
	private final List<FieldColorMap> colorMap = new ArrayList<FieldColorMap>();
	
	private final Color[] defaultFieldBackground = new Color[]{
			new Color(220, 220, 220),
			new Color(200, 200, 210)
	};
	
	private DefaultTableCellRenderer renderer = new DefaultTableCellRenderer() {
		private static final long serialVersionUID = 1774299984821460493L;

		public Component getTableCellRendererComponent(JTable table, Object value,
			    boolean isSelected, boolean hasFocus, 
			    int row, int column) {
			Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);		
			int idx = getByteIndex(row, column); 
			
			boolean selected = HexTable.this.isCellSelected(row, column); 

			this.setHorizontalAlignment(JLabel.CENTER);
			
			if (selected) 
			{
				component.setForeground(table.getSelectionForeground());
				component.setBackground(table.getSelectionBackground());
			}
			else {
				TreePageMetadataHelper metadata = getHexModel().getMetadata();
				FieldDescriptor field = metadata.getPage().getField(idx, false);
				if (field != null) 
				{
					boolean inactive = isFieldActive(field);
					
					FieldColorMap map = getFieldMap(field);
					if (map != null) {
						component.setForeground(map.getForeground());
						component.setBackground(inactive ? map.getBackground() : table.getSelectionBackground());
					}
					else {
						component.setForeground(table.getForeground());
						
						Color bg = defaultFieldBackground[field.getIndex() % defaultFieldBackground.length];
						component.setBackground(inactive ? bg : table.getSelectionBackground());
					}
				}
				else {
					component.setForeground(table.getBackground());
					component.setBackground(table.getBackground());
				}
			}
			
			return component;
		}
	};
	
	//private DefaultCellEditor editor = new DefaultCellEditor(new HexField(2));
	
	private DefaultCellEditor editor = new HexTableCellEditor();
	
	public HexTable() {
		this.setShowHorizontalLines(false);
		this.setShowVerticalLines(false);
		
//		this.setAutoResizeMode(AUTO_RESIZE_OFF);
		//this.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
		
		this.setColumnSelectionAllowed(true);
		
		setModel(new HexTableModel());
		
		this.getHexModel().addTableModelListener(new TableModelListener(){

			private int lastColumnCount = -1;
			
			public void tableChanged(TableModelEvent e) {
				if (lastColumnCount != HexTable.this.getColumnCount()) {
					formatColumns();
					lastColumnCount = HexTable.this.getColumnCount();
				}
			}			
		});
		
		formatColumns();
		
		Color idBgColor = new Color(170,170,255);
		colorMap.add(new FieldColorMap(new FieldTypeSignature(IDField.class), Color.BLACK, idBgColor));
		
		this.getSelectionModel().addListSelectionListener(new ListSelectionListener(){
			public void valueChanged(ListSelectionEvent e) {
				repaintActiveField();
			}			
		});
		
		this.getColumnModel().getSelectionModel().addListSelectionListener(new ListSelectionListener(){
			public void valueChanged(ListSelectionEvent e) {
				repaintActiveField();
			}			
		});
		
		this.setSelectionBackground(new Color(200, 255, 255));
		
		getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("ENTER"), "selectNextColumnCell");
		getInputMap(WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke("shift pressed ENTER"), "selectPreviousColumnCell");
		
		
		this.addKeyListener(new KeyListener(){
			public void keyPressed(KeyEvent e) {}

			@Override
			public void keyReleased(KeyEvent e) {}

			@Override
			public void keyTyped(KeyEvent e) {
				if (!getHexModel().isEdit()) {
					fireFieldAction();
				}
			}
		});
		
		
		this.addMouseListener(new MouseListener(){

			@Override
			public void mouseClicked(MouseEvent e) {
				if (!getHexModel().isEdit() && e.getClickCount() == 2) {
					fireFieldAction();
				}
			}

			public void mouseEntered(MouseEvent e) {}
			public void mouseExited(MouseEvent e) {}
			public void mousePressed(MouseEvent e) {}
			public void mouseReleased(MouseEvent e) {}
			
		});
	}
	
	public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
		return listenerList.getListeners(listenerType);
	}

    public void addFieldSelectionListener(FieldSelectionListener l) {
        listenerList.add(FieldSelectionListener.class, l);
    }

    public void removeFieldSelectionListener(FieldSelectionListener l) {
        listenerList.remove(FieldSelectionListener.class, l);
    }
    
    public void fireFieldSelection(FieldSelectionEvent e) {
    	Object[] listeners = listenerList.getListenerList();
    	for (int i = listeners.length-2; i>=0; i-=2) {
    		if (listeners[i] == FieldSelectionListener.class) {
    			((FieldSelectionListener)listeners[i+1]).fieldSelected(e);
    		}
    	}
    }
    
		
	public void fireFieldAction() {
		int row = getSelectedRow();
		int col = getSelectedColumn();
		
		if (col > 0)  {
			int idx = getByteIndex(row, col);
			TreePageMetadataHelper metadata = getHexModel().getMetadata();
			FieldDescriptor field = metadata.getPage().getField(idx, false);
			
			if (field != null) {
				fireFieldSelection(new FieldSelectionEvent(this, field, getHexModel().getPage(), -1));
			}
		}
	}

	protected void repaintActiveField() {
		repaint();
	}

	protected boolean isFieldActive(FieldDescriptor field) {
		int columns = this.getHexModel().getColumns();
		
		int ptr = field.getField().Ptr();
		int size = field.getField().Size() * field.getField().Count();
		for (int idx = ptr; idx < ptr + size; idx++) {
			int col = (idx % columns) + 1;
			int row = idx / columns;
			
			if (this.isCellSelected(row, col)) {
				return false;
			}
		}
		
		return true;
	}

	protected FieldColorMap getFieldMap(FieldDescriptor field) {
		for (FieldColorMap map: colorMap) {
			if (map.getSignature().accept(field)) {
				return map;
			}
		}
		return null;
	}
	
	public int getByteIndex(int row, int column) {
		int columns = getHexModel().getColumns();
		return row * columns + column - 1;
	}

	@Override
	public TableCellRenderer getCellRenderer(int row, int column) {
		if (column > 0) {
			int idx = getByteIndex(row, column); 
			Page page = getHexModel().getPage();
			if (idx >= 0 && idx < page.Size()) {
				return renderer;
			}
			else {
				return super.getCellRenderer(row, column);
			}
		}
		else {
			return super.getCellRenderer(row, column);
		}
	}
	
	@Override
	public TableCellEditor getCellEditor(int row, int column) {
		if (column > 0) {
			return editor;
		}
		else 
			return super.getCellEditor(row, column);
	}
	
	
	
	private void formatColumns() {
//		this.getColumnModel().setColumnMargin(5);
		
		TableColumn column = this.getColumnModel().getColumn(0);
		
		int w = 50;
		column.setMinWidth(w);
		column.setMaxWidth(w);
		column.setWidth(w);
		column.setResizable(false);
		
//		int columns = this.getColumnCount();
//		for (int c = 0; c < columns; c++) {
//			TableColumn column = this.getColumnModel().getColumn(c); 
//			column.setResizable(false);
//			
//			int w = c == 0 ? 50 : 30;
//			column.setMinWidth(w);
//			column.setMaxWidth(w);
//			column.setWidth(w);
//		}
	}
	
	public HexTableModel getHexModel() {
        return (HexTableModel) getModel();
    }
	
	@Override
	public void editingStopped(ChangeEvent e) {
		super.editingStopped(e);
		KeyEvent ke = new KeyEvent(this, KeyEvent.KEY_PRESSED, 0, 0, KeyEvent.VK_ENTER, '\n');
		processKeyEvent(ke);
	}

	public void setEditMode(boolean selected) {
		getHexModel().setEditMode(selected);
	}
}

